#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/errno.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/delay.h> // For msleep()
#ifdef S3C2440
#include <mach/gpio-samsung.h>
#else
#include <cfg_type.h>
#endif

static dev_t  led_dev_id;
static struct cdev led_cdev;
static struct class *led_class;
static struct device *led_device;

enum {led_0, led_1, led_2, led_3, _led_max};

static int led_pins[] = {
#ifdef S3C2440
	S3C2410_GPB(5),
	S3C2410_GPB(6),
	S3C2410_GPB(7),
	S3C2410_GPB(8),
#else
	PAD_GPIO_B + 26,
	PAD_GPIO_C + 11,
	PAD_GPIO_C + 7,
	PAD_GPIO_C + 12,
#endif
};

static ssize_t led_single_write(struct file *filp, const char __user *buf, size_t buf_size, loff_t *offset)
{
	int ret = 0;
	int led_no, val;
	char tmp[2];
	int i;

	ret = copy_from_user(tmp, buf, 2);
	if (ret < 0) {
		return -EFAULT;
	}

	led_no = tmp[0] - '0';
	val = tmp[1] - '0';
	val = !val;

	if ((led_no >= 0) && (led_no < _led_max)) {
		gpio_direction_output(led_pins[led_no], val);
		ret = buf_size;
	} else if (led_no == _led_max) {
		for (i = 0; i < _led_max; i++) {
			gpio_direction_output(led_pins[i], val);
			ret = buf_size;
		}
	}

	if (ret == 0) {
		ret = -EINVAL;
	}

	return ret;
}

static struct file_operations led_file_ops = {
	.write = led_single_write,
};

static int __init led_single_init(void)
{
	int ret;
	int i;

	ret = alloc_chrdev_region(&led_dev_id, 0, 1, "led_single");
	if (ret < 0) {
		printk("regist device id failed.\n");
		goto err_1;
	}

	cdev_init(&led_cdev, &led_file_ops);
	ret = cdev_add(&led_cdev, led_dev_id, 1);
	if (ret < 0) {
		printk("cdev add failed.\n");
		goto err_2;
	}

	led_class = class_create(THIS_MODULE, "led_single");
	led_device = device_create(led_class, NULL, led_dev_id, NULL, "leds");

	for (i = 0; i < _led_max; i++) {
		ret = gpio_request(led_pins[i], "LED");
		if (ret) {
			printk("%s: request GPIO %d for LED failed, ret = %d\n", "leds", led_pins[i], ret);
			return ret;
		}

		gpio_direction_output(led_pins[i], 0);
		msleep(20);
		gpio_direction_output(led_pins[i], 1);
	}

err_2:
	unregister_chrdev_region(led_dev_id, 1);	
err_1:
	return ret;
}

static void __exit led_single_exit(void)
{
	int i;

	for (i = _led_max - 1; i >= 0; i--) {
		gpio_direction_output(led_pins[i], 0);
		msleep(20);
		gpio_direction_output(led_pins[i], 1);
		gpio_free(led_pins[i]);
	}

	device_destroy(led_class, led_dev_id);
	class_destroy(led_class);
	cdev_del(&led_cdev);
	unregister_chrdev_region(led_dev_id, 1);
}

module_init(led_single_init);
module_exit(led_single_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YYW");
